package concurrent;

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

public class TheDiningPhilosophers1226 {

    // 方案1: 竞争信号量, 关键还是避免死锁
    class DiningPhilosophers {
        final int PHS = 5;
        final int NONE = -1;
        final int STATE_PICK = 1;
        final int STATE_EAT = 2;
        final int STATE_PUT = 3;
        final int STATE_FINISH = 4;
        final Semaphore[] sems;
        final int[] owners;

        public DiningPhilosophers() {
            sems = new Semaphore[PHS];
            owners = new int[PHS];
            for (int i = 0; i < PHS; i++) {
                sems[i] = new Semaphore(1);
                owners[i] = NONE;
            }
        }

        public int lockCount() {
            int count = 0;
            for (int i = 0 ; i < PHS; i++) {
                if (owners[i] != NONE) {
                    count++;
                }
            }
            return count;
        }

        public int myLockCount(int ph) {
            int count = 0;
            for (int i = 0 ; i < PHS; i++) {
                if (owners[i] == ph) {
                    count++;
                }
            }
            return count;
        }

        public void wantsToEat(int ph,
                                Runnable pickLeftFork,
                                Runnable pickRightFork,
                                Runnable eat,
                                Runnable putLeftFork,
                                Runnable putRightFork) throws InterruptedException {
            int left = ph;
            int right = (ph+1) % sems.length;
            int state = STATE_PICK;

            while (true) {
                // 避免死锁, 必须这样
                if (lockCount() >= PHS - 1 && myLockCount(ph) == 0) {
                    continue;
                }

                if (state == STATE_PICK) {
                    if (owners[left] == NONE) {
                        if (sems[left].tryAcquire(1)) {
                            owners[left] = ph;
                            pickLeftFork.run();
                        }
                    }
                    if (owners[right] == NONE) {
                        if (sems[right].tryAcquire(1)) {
                            owners[right] = ph;
                            pickRightFork.run();
                        }
                    }
                    if (myLockCount(ph) == 2) {
                        state = STATE_EAT;
                    }
                } else if (state == STATE_EAT) {
                    eat.run();
                    state = STATE_PUT;
                } else if (state == STATE_PUT) {
                    if (owners[left] == ph) {
                        owners[left] = NONE;
                        putLeftFork.run();
                        sems[left].release(1);
                    }
                    if (owners[right] == ph) {
                        owners[right] = NONE;
                        putRightFork.run();
                        sems[right].release();
                    }
                    state = STATE_FINISH;
                } else {
                    return;
                }
            }
        }
    }

    // 方案2: 有序抢锁. 调一次要吃掉一次, 产生合法的序列
    class DiningPhilosophers2 {
        final int PHS = 5;
        final int NONE = -1;
        final int STATE_PICK = 1;
        final int STATE_EAT = 2;
        final int STATE_PUT = 3;
        final int STATE_FINISH = 4;
        final ReentrantLock[] locks;
        final int[] owners;

        public DiningPhilosophers2() {
            locks = new ReentrantLock[PHS];
            owners = new int[PHS];
            for (int i = 0; i < PHS; i++) {
                locks[i] = new ReentrantLock();
                owners[i] = NONE;
            }
        }

        public int lockCount() {
            int count = 0;
            for (ReentrantLock lock: locks) {
                if (lock.isLocked()) {
                    count++;
                }
            }
            return count;
        }

        public int myLockCount(int ph) {
            int count = 0;
            for (int i = 0 ; i < PHS; i++) {
                if (owners[i] == ph) {
                    count++;
                }
            }
            return count;
        }

        public void wantsToEat(int ph,
                               Runnable pickLeftFork,
                               Runnable pickRightFork,
                               Runnable eat,
                               Runnable putLeftFork,
                               Runnable putRightFork) throws InterruptedException {
            int left = ph;
            int right = (ph+1) % locks.length;
            int state = STATE_PICK;

            while (true) {
                if (lockCount() >= PHS-1 && myLockCount(ph) == 0) {
                    continue;
                }
                if (state == STATE_PICK) {
                    if (owners[left] == NONE) {
                        if (locks[left].tryLock()) {
                            owners[left] = ph;
                            pickLeftFork.run();
                        }
                    }
                    if (owners[right] == NONE) {
                        if (locks[right].tryLock()) {
                            owners[right] = ph;
                            pickRightFork.run();
                        }
                    }
                    if (myLockCount(ph) == 2) {
                        state = STATE_EAT;
                    }
                } else if (state == STATE_EAT) {
                    eat.run();
                    state = STATE_PUT;
                } else if (state == STATE_PUT) {
                    if (owners[left] == ph) {
                        owners[left] = NONE;
                        putLeftFork.run();
                        locks[left].unlock();
                    }
                    if (owners[right] == ph) {
                        owners[right] = NONE;
                        putRightFork.run();
                        locks[right].unlock();
                    }
                    state = STATE_FINISH;
                } else {
                    return;
                }
            }
        }
    }
}
